home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Utilities / Remotes / Source / BoxViewSwitcher.m < prev    next >
Encoding:
Text File  |  1992-09-23  |  17.8 KB  |  496 lines

  1. /*---------------------------------------------------------------------------
  2. BoxViewSwitcher.m -- switch between box views using a popup list (button)
  3.  
  4. Copyright (c) 1990 Doug Brenner
  5.  
  6.    This program is free software; you can redistribute it and/or modify
  7.    it under the terms of the GNU General Public License as published by
  8.    the Free Software Foundation; either version 1, or (at your option)
  9.    any later version.
  10.  
  11.    This program is distributed in the hope that it will be useful,
  12.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.    GNU General Public License for more details.
  15.  
  16.    You should have received a copy of the GNU General Public License
  17.    along with this program; if not, write to the Free Software
  18.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or send
  19.    electronic mail to the the author.
  20.  
  21. Implementation for the BoxViewSwitcher class.  BoxViewSwitcher allows you to
  22. switch between a group of views using a popup button.  (Something like the
  23. popup button on the Inspector panel of Interface Builder.)
  24.  
  25. *** Using BoxViewSwitcher
  26.  
  27. This is the easy part because you do everything in Interface Builder!  Here
  28. is a step-by-step guide meant for people familiar with Interface Builder.
  29.  
  30. 1. Setup: Launch IB, create a new application, create a project, and save.
  31.  
  32. 2. Main window setup: You should already have one window from when you
  33. created the new application.  This will be the main window with which people
  34. will interacte.  On this window create one button and one box view.  Within
  35. the box view put any controls that you want and give the box view a name.
  36.  
  37. This box view will be the "default" box view that people will see when your
  38. application is launched.
  39.  
  40. 3. Alternate window setup: Create another window.  (I'll refer to this window
  41. as the "alternate" window and the window from step 2 as the "main" window.)
  42. Select and copy the box view from the main window and paste it onto this
  43. alternate window.  Give this newly pasted box view a different name and
  44. update it's controls, etc., to your needs.
  45.  
  46. If you want more than two alternate views, repeat the copy/paste/udpate
  47. process until you have all you need.  BoxViewSwitcher will locate all the box
  48. views at the "top level" of the alternate window.  (In other words, it
  49. doesn't descend the view hierarchy looking for box views within views.)
  50.  
  51. 4. Bring in BoxViewSwitcher: Open the Classes panel and create a subclass of
  52. Object.  (Make sure it's a subclass of object.)  Rename this subclass as
  53. BoxViewSwitcher.  Drag copies of BoxViewSwitcher.h and BoxViewSwitcher.m
  54. into the same folder as this Interface Builder project.
  55.  
  56. Choose Parse from the Classes popdown button. (Click "Replace" since you know
  57. the outlets, etc., are different.)
  58.  
  59. Choose Instantiate from the Classes popdown button.  You should see a new
  60. instance (probably named BoxViewSwitcherInstance) in the objects window.
  61.  
  62. Add BoxViewSwitcher.[hm] to the Files section of the Project window.
  63.  
  64. 5. Attach the BoxViewSwitcher instance outlets: There are only three outlets
  65. that you need connect: controlButton, boxView, and boxViewWindow.  (Ignore
  66. boxViewList and popup.)
  67.  
  68. Connect the BoxViewSwitcher instance outlets as follows:
  69.  
  70.    Outlet Name     Connect to:
  71.    -------------   -------------------------------------------------
  72.    controlButton   button on the main window
  73.    boxView         box view on the main window
  74.    boxViewWindow   alternate window of box views (the entire window)
  75.  
  76. (If you want the instantiated BoxViewSwitcher to send delegate methods [see
  77. setDelegate:], connect some other custom object to the delegate outlet.)
  78.  
  79. 6. Make everything: Save and then make the application.
  80.  
  81. *** Hints
  82.  
  83. You can connect the controls in any of the box views to your own custom
  84. objects or other controls.  These connections will be maintained even after
  85. the box views "move" from one window to the other.
  86.  
  87. In general you want all the box views to be the same size and to have unique
  88. names.  The alternate box view window should have "Visible at Launch Time"
  89. (in the Inspector panel) unchecked.
  90.  
  91. You can also create menu items to select between the various box views.  Just
  92. name the menu item the same as the desired box view title and connect the
  93. menu item to BoxViewSwitcher's selectBoxView: method.
  94.  
  95. All three outlets (controlButton, boxView, boxViewWindow) must be connected,
  96. and connected to the proper classes or nothing will happen.
  97.  
  98. BoxViewSwitcher assumes the alternate window is for its use only.  After it
  99. collects all the box views, the alternate window is freed.  (I.e., if you
  100. need a place for other things, don't put them on the alternate window.)
  101.  
  102. *** Other controls and the selectBoxView: method
  103.  
  104. BoxViewSwitcher only knows about one control, controlButton.  This means only
  105. controlButton is always updated to reflect which box view is currently
  106. displayed.  Why is this important?
  107.  
  108. Let's say you attach a radio button matrix to the selectBoxView: method;
  109. things will look fine at first.  The matrix will correctly select between the
  110. various box views based on which button is selected.  The problem shows up
  111. when you use the controlButton/popup combination to select a box view.
  112.  
  113. Because BoxViewSwitcher doesn't know about your radio button matrix, it
  114. isn't updated after a new selection is made.  That is the problem.
  115.  
  116. To deal with this situation, the delegate method boxViewDidSwitch:to: has
  117. been supplied.  Use it to update other controls you might use to select
  118. between box views.
  119.  
  120. To solve the radio button problem given above, you might try something like
  121. this in your delegate:
  122.  
  123.    #import <appkit/Button.h>
  124.    #import <appkit/Matrix.h>
  125.    #import <appkit/Box.h>
  126.    #import <objc/List.h>
  127.  
  128.    - boxViewDidSwitch:sender
  129.    {
  130.       id cells, cell;
  131.       int i;
  132.       const char *newTitle;
  133.  
  134.       cells = [radioButtonMatrix cellList];
  135.       for (i = [cells count]-1; i >= 0; i--) {
  136.          cell = [cells objectAt:i];
  137.          newTitle = [[sender boxView] title];
  138.          if (strcmp ([cell title], newTitle) == 0) {
  139.             [radioButtonMatrix selectCell:cell];
  140.             break;
  141.          }
  142.       }
  143.       return self;
  144.    }
  145.    
  146.    return self;
  147.  
  148. And then again, you may find the above code silly.  Use it as you see fit.
  149.  
  150. Doug Brenner <dbrenner@umaxc.weeg.uiowa.edu>
  151.  
  152. $Header: /rpruess/apps/Remotes3.0/RCS/BoxViewSwitcher.m,v 3.0 92/09/23 22:16:54 rpruess Exp $
  153. -----------------------------------------------------------------------------
  154. $Log:    BoxViewSwitcher.m,v $
  155. Revision 3.0  92/09/23  22:16:54  rpruess
  156. Checked in to RCS to get the revision number updated to 3.0.
  157.  
  158. Revision 2.1  92/09/23  21:29:31  rpruess
  159. Updated code for NeXT System Release 3.0.
  160.  
  161. Revision 2.0  91/01/22  15:24:01  rpruess
  162. Incorporated into Remotes at Release 2.0.
  163.  
  164. Revision 2.0  91/01/22  13:13:36  rpruess
  165. Initial production release of Remotes-2.0.
  166.  
  167. Revision 2.0  90/06/22  18:28:39  dbrenner
  168. Comment changes; added delegate notification in selectBoxViewTitle: (this
  169. required a new instance variable, delegate, and method (setDelegate:);
  170. added methods to return most instance variables (boxViewList, boxView,
  171. popup, controlButton); moved code to force the controlButton title from
  172. replaceBoxViewWith: to selectBoxViewTitle: (this seems more reasonable);
  173. added archive methods (read and write); added class method initialize
  174. to set the class version number.
  175.  
  176. Revision 1.3  90/06/05  17:06:08  dbrenner
  177. Minor comment changes for automatic documentation generation.
  178.  
  179. Revision 1.2  90/05/26  14:52:16  dbrenner
  180. Many more comments; added code to update the controlButton title after
  181. a switch has been made (in case things were done via another control).
  182.  
  183. Revision 1.1  90/05/18  21:12:44  dbrenner
  184. Initial revision
  185.  
  186. -----------------------------------------------------------------------------*/
  187.  
  188. #import <appkit/Button.h>
  189. #import <appkit/Window.h>
  190. #import <appkit/Box.h>
  191. #import <appkit/PopUpList.h>
  192. #import <appkit/View.h>
  193. #import <objc/List.h>
  194.  
  195. #include <string.h>
  196.  
  197. #import "BoxViewSwitcher.h"
  198.  
  199. @implementation BoxViewSwitcher
  200.  
  201. /*---------------------------------------------------------------------------
  202. The methods setControlButton:, setBoxViewWindow:, and setBoxView: set the
  203. outlets controlButton, boxViewWindow, and boxView, respectively.  (Connect
  204. these outlets via Interface Builder.)
  205.  
  206. In all cases, if the id received is of the proper type, its value is stored;
  207. otherwise, the method just returns.
  208.  
  209. After storing the id, the setup method is called.  Since you cannot (and
  210. should not) depend on the order in which ids are supplied, setup is called
  211. every time but doesn't do anything until all three outlets are initialized.
  212. -----------------------------------------------------------------------------*/
  213. - setControlButton: anObject
  214. {
  215.    if ([anObject isKindOf:[Button class]]) {
  216.       controlButton = anObject;
  217.       [self setup];
  218.    }
  219.    return self;
  220. }
  221.  
  222. - setBoxViewWindow: anObject
  223. {
  224.    if ([anObject isKindOf:[Window class]]) {
  225.       boxViewWindow = anObject;
  226.       [self setup];
  227.    }
  228.    return self;
  229. }
  230.  
  231. - setBoxView: anObject
  232. {
  233.    if ([anObject isKindOf:[Box class]]) {
  234.       boxView = anObject;
  235.       [self setup];
  236.    }
  237.    return self;
  238. }
  239.  
  240. /*---------------------------------------------------------------------------
  241. This method sets the BoxViewSwitcher's delegate to anObject.
  242.  
  243. After a switch is made, the message boxViewDidSwitchTo: is sent to the delegate
  244. with the title of the new box view.  This message is informative only; the
  245. return value is ignored.
  246. ---------------------------------------------------------------------------*/
  247. - setDelegate: anObject { delegate = anObject; return self;}
  248.  
  249. /*---------------------------------------------------------------------------
  250. These methods return instance variable information.
  251. ---------------------------------------------------------------------------*/
  252. - boxViewList { return boxViewList; }
  253. - popup { return popup; }
  254. - boxView { return boxView; }
  255. - controlButton { return controlButton; }
  256.  
  257. /*---------------------------------------------------------------------------
  258. This method sets everything up.  In particular it does the following:
  259.  
  260. Creates a popup list of possible box view titles.  (The titles are from
  261. boxView's title and from the title of every box view in the contentView of
  262. boxViewWindow.)
  263.  
  264. Populates boxViewList with the ids of the possible box views.  (This allows
  265. easy searching and a provides a storage locate for the views after
  266. boxViewWindow is freed.  [This list includes the original boxView.])
  267.  
  268. Sets the popup list target/action and connects the popup to controlButton.
  269. (The popup list sends selectBoxView: to BoxViewSwitcher when something is
  270. selected, hence we set it's target and action appropriately.  [These could
  271. possibly be over-ridden if controlButton already has a target and action.
  272. See the discussion under NXAttachPopUpList in the PopUpList specification.])
  273.  
  274. Frees boxViewWindow after removing the box views from its view hierarchy.
  275. (This reduces memory requirements somewhat.  It is assumed that boxViewWindow
  276. is nothing more than a convient place for you to design and store the
  277. alternate box views.  Once collected into boxViewList, the window is useless
  278. to BoxViewSwitcher.)
  279. -----------------------------------------------------------------------------*/
  280. - setup
  281. {
  282.    int i;
  283.    id views;            /* list of subviews in boxViewWindow */
  284.    id oneView;            /* one view from the above list */
  285.    
  286.  
  287.    /* ---------- we need all three outlets before we can setup */
  288.  
  289.    if (! (controlButton && boxViewWindow && boxView))
  290.       return self;
  291.  
  292.  
  293.    /* ---------- setup popup list and populate boxViewList */
  294.  
  295.    popup = [[PopUpList alloc] init];         /* popup list for controlButton */
  296.    boxViewList = [[List alloc] init];         /* we'll save the box views here */
  297.  
  298.    [popup addItem:[boxView title]];
  299.    [boxViewList addObject:boxView];
  300.  
  301.    views = [[boxViewWindow contentView] subviews];
  302.  
  303.    for (i = [views count]-1; i >= 0; i--) {
  304.       oneView = [views objectAt:i];
  305.       if ([oneView isKindOf:[Box class]]) {
  306.      [popup addItem:[oneView title]];
  307.      [boxViewList addObject:oneView];
  308.       }
  309.    }
  310.    
  311.    [[popup setTarget:self] setAction:@selector(selectBoxView:)];
  312.    
  313.  
  314.    /* ---------- setup controlButton */
  315.  
  316.    [controlButton setTitle:[boxView title]];
  317.    NXAttachPopUpList (controlButton, popup);    
  318.    
  319.  
  320.    /* ---------- dettach box views from boxViewWindow and free the window */
  321.  
  322.    views = [[boxViewWindow contentView] subviews];
  323.  
  324.    for (i = [views count]-1; i >= 0; i--)
  325.       [[views objectAt:i] removeFromSuperview];
  326.       
  327.    [boxViewWindow free];
  328.    boxViewWindow = nil;        /* to prevent problems */
  329.    
  330.    return self;
  331. }
  332.  
  333. /*---------------------------------------------------------------------------
  334. This is the primary interface for controls that have titles.  (Anything that
  335. uses ButtonCell is a good bet, e.g., Menu, PopUpList.)  This method simply
  336. queries the sender for its title (via [[sender selectedCell] title]) and
  337. uses selectBoxViewTitle: to do the real work.
  338.  
  339. If BoxViewSwitch did the setup, the method is called by the popup list that
  340. BoxViewSwitch created during the setup method.
  341. -----------------------------------------------------------------------------*/
  342. - selectBoxView: sender
  343. {
  344.    [self selectBoxViewTitle:[[sender selectedCell] title]];
  345.    return self;
  346. }
  347.  
  348. /*---------------------------------------------------------------------------
  349. This method searches boxViewList for a box view with the same title as the
  350. one passed in.  If a match is found, replaceBoxViewWith: is used to replace
  351. boxView with the found view.
  352.  
  353. The controlButton title is normally updated by the popup list, but this
  354. method also updates it to handle the case where some other control has made
  355. the selection.  (See the opening comments for more information about using
  356. other controls with BoxViewSwitcher.)
  357.  
  358. After the switch, the delegate is informed about the change.
  359. -----------------------------------------------------------------------------*/
  360. - selectBoxViewTitle:(const char *) title
  361. {
  362.    int i;
  363.    id thisView;
  364.    
  365.    if (! boxViewList || ! title) return self;
  366.    
  367.    for (i = [boxViewList count]-1; i >= 0; i--) {
  368.       thisView = [boxViewList objectAt:i];
  369.  
  370.       if (strcmp ([thisView title], title) == 0) {
  371.  
  372.      [self replaceBoxViewWith:thisView];
  373.  
  374.      [controlButton setTitle:[boxView title]];
  375.  
  376.      /* notify the delegate about the switch, if it wants to know */
  377.  
  378.      if ([delegate respondsTo:@selector(boxViewDidSwitch:)] == YES)
  379.         [delegate boxViewDidSwitch:self];
  380.  
  381.      break;
  382.       }
  383.    }
  384.  
  385.    return self;
  386. }
  387.  
  388. /*---------------------------------------------------------------------------
  389. This method replaces boxView with aView, forces aView into the same frame as
  390. boxView, and asks the superview to redisplay.
  391.  
  392. The frame of aView must be changed because the various view frames are most
  393. likely different.  Keep in mind that the alternate views start out on another
  394. window and have unknown frames.  It is also possible that boxView's window
  395. may have resized.  (It is this latter resizing issue that really matters
  396. since the alternate box view frames could have been forced during setup.)
  397.  
  398. It is assumed that the current boxView always has the proper frame, hence,
  399. its frame is forced upon aView.
  400.  
  401. And a final special note for those of you using View's replaceSubview:with:
  402. method.  Don't replace a view with itself.  This is a degenerate case that
  403. caused me a bit of confusion.  The following code, or its equivalent,
  404.  
  405.    [aSuperView replaceSubview:aView with:aView]
  406.  
  407. results in aView being removed from aSuperView's hierarchy.  (At least it
  408. happened at the time of this writing.)  Not necessarily what you might want.
  409. -----------------------------------------------------------------------------*/
  410. - replaceBoxViewWith: aView
  411. {
  412.    NXRect r;
  413.    
  414.    if (! boxView || ! aView) return self;
  415.  
  416.    if (boxView == aView || [aView isKindOf:[View class]] == NO)
  417.       return self;
  418.    
  419.    [boxView getFrame:&r];
  420.    [aView setFrame:&r];
  421.    
  422.    [[boxView superview] replaceSubview:boxView with:aView];
  423.    [[aView superview] display];
  424.    boxView = aView;
  425.    
  426.    return self;
  427. }
  428.  
  429. /*---------------------------------------------------------------------------
  430. Frees the popup list, all the views in boxViewList, and boxViewList.
  431.  
  432. The fact that all the views in boxViewList are freed seems to imply that
  433. BoxViewSwitcher "owns" them.  (This could be considered wrong.)
  434. -----------------------------------------------------------------------------*/
  435. - free
  436. {
  437.    [popup free];
  438.    [boxViewList freeObjects];
  439.    [boxViewList free];
  440.    [super free];
  441.    return self;
  442. }
  443.  
  444. /*---------------------------------------------------------------------------
  445. This method write the BoxViewSwitcher instance to the typed stream.
  446. ---------------------------------------------------------------------------*/
  447. - write:(NXTypedStream *) stream
  448. {
  449.    [super write:stream];
  450.  
  451.    /* ---------- version 1 variables */
  452.    NXWriteTypes (stream, "@@@", controlButton, boxViewWindow, boxView);
  453.    NXWriteTypes (stream, "@@", popup, boxViewList);
  454.  
  455.    /* ---------- version 2 variables */
  456.    NXWriteObjectReference (stream, delegate);
  457.  
  458.    return self;
  459. }
  460.  
  461. /*---------------------------------------------------------------------------
  462. This method reads the BoxViewSwitcher instance from the typed stream.
  463. ---------------------------------------------------------------------------*/
  464. - read: (NXTypedStream *) stream
  465. {
  466.    int streamVer;        /* stream class version */
  467.    
  468.    [super read:stream];
  469.  
  470.    streamVer = NXTypedStreamClassVersion (stream, "BoxViewSwitcher");
  471.    
  472.    /* ---------- version 1 variables */
  473.    NXReadTypes (stream, "@@@", controlButton, boxViewWindow, boxView);
  474.    NXReadTypes (stream, "@@", popup, boxViewList);
  475.  
  476.    /* ---------- version 2 variables */
  477.    if (streamVer >= 2) NXReadTypes (stream, "@", delegate);
  478.    
  479.    return self;
  480. }
  481.  
  482. /*---------------------------------------------------------------------------
  483. This class method sets the version number of the User class.  The current
  484. version number is 2; version number 1 did not implement this method.
  485.  
  486. This method should not be called directly.
  487. ---------------------------------------------------------------------------*/
  488. + initialize
  489. {
  490.    [super initialize];
  491.    [BoxViewSwitcher setVersion:2];
  492.    return self;
  493. }
  494.  
  495. @end
  496.